console.log('');

import executeSync from './utils/args/executeSync.mjs';

import extnames from './dependencies/extnames.mjs';
import dirs from './dependencies/dirs.mjs';
console.log('[I] =>', 'dirs:', dirs);

import path from 'path';
import fs from 'fs';

import unlinkSync from './utils/files/unlinkSync.mjs';
import mkdirSync from './utils/files/mkdirSync.mjs';
import copyDirSync from './utils/files/copyDirSync.mjs';
import copyFileSync from './utils/files/copyFileSync.mjs';
import readdirSync from './utils/files/readdirSync.mjs';

import { handleArgs } from './utils/args/args.mjs';
import { initWebpackArgsConfigs, webpackCMD } from './dependencies/args.webpack.mjs';
const webpackArgsConfigs = initWebpackArgsConfigs(false);

import is_entry_file from './dependencies/is_entry_file.mjs';
import replace_import from './dependencies/replace_import.mjs';

import { minify_sync } from 'terser';


// ====================================================== 命令执行方法初始化 ====================================================== //
const runners = {};
{
    // =========================== init =========================== //
    runners.init = {
        argsConfigs: {
            name: {
                t: 'string',
                defaultValue: 'kui-configs-demo',
                range: [],
            },
        },
        run: (args, options) => {
            const dir$project = path.join(path.dirname(dirs.base_project), args.name);

            // 创建项目目录
            if (!fs.existsSync(dir$project)) {
                console.log('[I] =>', `create project directory ${dir$project}`);
                mkdirSync(dir$project);
            }

            // 从模板生成
            const dir$templates = path.join(dirs.base_project, 'templates');
            readdirSync(dir$templates, true, true, true).forEach(filePath => {
                const destFilePath = path.join(dir$project, filePath.substring(dir$templates.length + 1));
                if (fs.existsSync(destFilePath)) { return; }// 存在不替换
                const filename = path.basename(filePath);
                if (filename === 'package' && fs.existsSync(destFilePath + '.json')) { return; }// 存在不替换

                const stats = fs.statSync(filePath);
                if (stats.isFile()) {
                    console.log('[I] =>', 'copy file to:', destFilePath);
                    copyFileSync(filePath, destFilePath);

                    // package.json变量替换与重命名
                    if (filename === 'package') {// 模板中去掉扩展名（因编码工具会扫描到该文件中的scripts）
                        console.log('[I] =>', 'repalce arguments in file:', destFilePath);
                        fs.writeFileSync(destFilePath, fs.readFileSync(destFilePath, 'utf8').replace('${name}', args.name), 'utf8');

                        const destFilePath$ = destFilePath + '.json';
                        console.log('[I] =>', 'rename file from', destFilePath, 'to', destFilePath$);
                        fs.renameSync(destFilePath, destFilePath$);
                    }

                    // README.md变量替换
                    if (filename === 'README.md') {
                        console.log('[I] =>', 'repalce arguments in file:', destFilePath);
                        fs.writeFileSync(destFilePath, fs.readFileSync(destFilePath, 'utf8').replace('${name}', args.name), 'utf8');
                    }

                    // .gitignore重命名
                    if (filename === 'gitignore') {// 使用原名打包base项目时会丢失
                        const destFilePath$ = destFilePath.slice(0, -filename.length) + '.' + filename;
                        console.log('[I] =>', 'rename file from', destFilePath, 'to', destFilePath$);
                        fs.renameSync(destFilePath, destFilePath$);
                    }

                    // .npmignore重命名
                    if (filename === 'npmignore') {// 使用原名打包base项目时会丢失
                        const destFilePath$ = destFilePath.slice(0, -filename.length) + '.' + filename;
                        console.log('[I] =>', 'rename file from', destFilePath, 'to', destFilePath$);
                        fs.renameSync(destFilePath, destFilePath$);
                    }

                    return;
                }
                if (stats.isDirectory()) {
                    console.log('[I] =>', `make directory: ${destFilePath}`);
                    mkdirSync(destFilePath);
                    return;
                }
            });

            // 创建符号链接
            const node_modules = path.join(dir$project, dirs.name$node_modules);
            if (!fs.existsSync(node_modules)) {
                const exeResult = executeSync(`mklink /D "${node_modules}" "${dirs.node_modules}"`);
                if (!exeResult) {
                    console.log('[E] =>', `failed to make directory: ${node_modules}`);
                }
            }
        }
    };



    // =========================== tsc =========================== //
    runners.tsc = {
        argsConfigs: {
            include: {
                t: 'string[]',
                defaultValue: [],
                range: [],
            },
            exclude: {
                t: 'string[]',
                defaultValue: [],
                range: [],
            },
        },
        run: (args, options) => {
            const _extnames_ = [...extnames.js, ...extnames.ts];
            const dir$out = path.join(dirs.run_project, options.to ? options.to : path.join('dist', 'tsc'));
            const dir$types = path.join(dirs.run_project, 'types');

            // 删除build目录
            if (fs.existsSync(dir$out)) {
                console.log('[I] =>', `remove codes directory: ${dir$out}`);
                fs.rmSync(dir$out, { recursive: true });
            }

            // 删除types目录
            if (options.declaration && fs.existsSync(dir$types)) {
                console.log('[I] =>', `remove types directory: ${dir$types}`);
                fs.rmSync(dir$types, { recursive: true });
            }

            // 复制源码到目标目录
            const dir$in = path.join(dirs.run_project, 'src', 'js');
            const tsFiles = [];
            let hasEntryTs = false;// 是否有入口的TS文件，tsc命令在没有入口TS文件时，会忽略模块目录。
            readdirSync(dir$in, true).forEach(filePath => {
                const extname = path.extname(filePath);
                const isJS = extnames.js.includes(extname);
                const isTS = extnames.ts.includes(extname);
                if (!isJS && !isTS) { return; }// 非脚本文件，不处理。

                const filename = filePath.substring(dir$in.length + 1);
                const filenameWithoutExtname = filename.slice(0, -(extname.length));
                const isEntryFile = filename === path.basename(filePath);// 在根目录下视为入口文件

                // 校验入口文件的包含和排除逻辑
                if (isEntryFile && !is_entry_file(filenameWithoutExtname, _extnames_, args.include, args.exclude)) {
                    console.log('[I] =>', 'file: [', filename, '] skiped by include or exclude rules.');
                    return;
                }

                const filePathTo = path.join(dir$out, filename);
                const filePathToWithoutExt = filePathTo.slice(0, -(extname.length));

                // MTS MJS之类的文件都将影响最终发布结果，在调试阶段就要执行防错。
                const filePathTo$rename = filePathToWithoutExt + (isTS ? '.ts' : '.js');
                // 不允许重名但不同后缀名文件存在
                if (fs.existsSync(filePathTo$rename)) {
                    throw new Error('file:', filePathTo$rename, ' with the same name but different extensions are not allowed to exist.');
                }

                // 复制源码
                console.log('[D] =>', 'copy file: [', filePath, '] to [', filePathTo, '].');
                copyFileSync(filePath, filePathTo);

                // 修改后缀名
                if (filePathTo !== filePathTo$rename) {
                    console.log('[D] =>', 'rename file: [', filePathTo, '] to [', filePathTo$rename, '].');
                    fs.renameSync(filePathTo, filePathTo$rename);
                }

                // 相关记录内容处理
                if (isTS) {
                    if (isEntryFile) { hasEntryTs = true; }// 有入口TS文件
                    tsFiles.push(filePathTo$rename);// 加入到编译命令中
                    return;
                }
            });

            // 修改文件内容的import后缀
            readdirSync(dir$out, true).forEach(filePath => {
                fs.writeFileSync(filePath, replace_import.call({ tsc: true, resourcePath: filePath }, fs.readFileSync(filePath, 'utf8')), 'utf8');
            });

            // exeute tsc
            executeTSC: {
                // 没有新增需要编译的ts文件，则不执行编译命令。
                if (tsFiles.length === 0) {
                    console.log('[I] =>', 'there are no files to be compiled by tsc.');
                    break executeTSC;
                }

                // 执行ts代码编译
                const cmds = [];
                cmds.push('tsc');
                cmds.push('--outDir');
                cmds.push(hasEntryTs ? dir$out : path.join(dir$out, 'modules'));
                cmds.push('--strict true');
                cmds.push('--target esnext');
                cmds.push('--module preserve');
                cmds.push('--noImplicitAny false');
                cmds.push('--verbatimModuleSyntax true');
                if (options.declaration) {// 发布包需要创建描述文件
                    cmds.push('--declaration true');
                    cmds.push('--declarationDir');
                    cmds.push(hasEntryTs ? dir$types : path.join(dir$types, 'modules'));
                }
                const tscResult = executeSync(cmds.concat(tsFiles).join(' '));

                // 编译失败时，直接结束。
                if (!tscResult) {
                    console.log('[E] =>', 'tsc failed.');
                    throw new Error('tsc failed.');
                }

                // 删除ts代码文件
                console.log('[I] =>', 'tsc success. remove ts files.');
                tsFiles.forEach(filePath => {
                    console.log('[D] =>', 'remove file:', filePath);
                    unlinkSync(filePath);
                });
                console.log('');

                if (options.declaration) {// 发布包需要创建汇总描述文件index.d.ts
                    const file$index = 'index.d.ts';
                    console.log('[I] =>', 'create file:', file$index);
                    const content = [];
                    readdirSync(dir$types, true).forEach(filePath => {
                        const content$dfile = fs.readFileSync(filePath, 'utf8');
                        if (content$dfile.trim() === '') { return; }// 空.d.ts文件，没有导出项。
                        if (!content$dfile.includes('export')) {// 非模块，与空.d.ts文件不同，内容中有编译内容，暂时处理为注释掉。// TODO 何种情况需要特殊处理
                            const content$dfile_new = [];
                            content$dfile_new.push('export {};');// 导出空对象
                            // 注释原编译结果
                            content$dfile_new.push('\r\n');
                            content$dfile_new.push('\r\n');
                            content$dfile_new.push('/**');
                            content$dfile_new.push('\r\n');
                            content$dfile_new.push('\r\n');
                            content$dfile_new.push(content$dfile);
                            content$dfile_new.push('\r\n');
                            content$dfile_new.push('*/');
                            content$dfile_new.push('\r\n');
                            // 修改文件内容
                            fs.writeFileSync(filePath, content$dfile_new.join(''), 'utf-8');
                        }
                        content.push(`export * from "./${filePath.slice((dir$types.length + 1), -('.d'.length + path.extname(filePath).length)).split('\\').join('/')}";`);
                    });
                    fs.writeFileSync(path.join(dir$types, file$index), content.join('\r\n'), 'utf8');
                }
            }

            if (options.simulators) {
                // simulators内容处理
                const content = [];
                content.push('// =========================== simulators =========================== //');
                const name$simulators = 'simulators';
                const simulatorsDir = path.join(dirs.root, 'utils', name$simulators);
                readdirSync(simulatorsDir, true).forEach(filePath => {
                    const relative_filename = filePath.substring(simulatorsDir.length + 1);
                    content.push(`import { } from './${name$simulators}/${relative_filename}';`);// 入口文件导入模拟器代码
                    copyFileSync(filePath, path.join(dir$out, name$simulators, relative_filename));// 模拟器代码复制
                });
                content.push('');
                content.push('// =========================== codes =========================== //');
                const simulatorsContent = content.join('\r\n') + '\r\n';

                // 入口文件增加导入模拟器代码
                readdirSync(dir$out, false).forEach(filePath => {
                    console.log('[I] =>', 'add simulators to entry file:', filePath);
                    fs.writeFileSync(filePath, simulatorsContent + fs.readFileSync(filePath, 'utf8'));
                });
            }

            return dir$out;
        }
    };



    // =========================== run =========================== //
    runners.run = {
        argsConfigs: runners.tsc.argsConfigs,
        run: (args, options) => {
            // 先编译
            const runResult = callrun('tsc', {}, {
                to: path.join('dist', 'tsc-run'),
                simulators: true,
            });

            // 到编译目录中执行
            let has_entry = false;
            readdirSync(runResult, false).forEach(filePath => {
                if (extnames.js.includes(path.extname(filePath))) {
                    if (!has_entry) { has_entry = true; }
                    executeSync(`node ${filePath}`);
                }
            });
            if (!has_entry) {
                console.log('[I] =>', 'there is no file available for execution.');
            }
        }
    };



    // =========================== webpack =========================== //
    runners.webpack = {
        argsConfigs: webpackArgsConfigs,
        run: (args, options) => {
            return executeSync(webpackCMD(args, options));
        }
    };



    // =========================== server =========================== //
    runners.server = {
        argsConfigs: (() => {
            const result = {};

            Object.keys(webpackArgsConfigs).forEach(key => {
                result[key] = Object.assign({}, webpackArgsConfigs[key]);
            });

            result.mode.defaultValue = 'development';
            result.babel.defaultValue = false;

            return result;
        })(),
        run: (args, options) => {
            options.server = true;
            return executeSync(webpackCMD(args, options));
        }
    };



    // =========================== pack =========================== //
    runners.pack = {
        argsConfigs: {
            type: {
                t: 'string',
                defaultValue: 'package',
                range: ['webpackage', 'package', 'webnext'],
            },
        },
        run: (args, options) => {
            // 非base项目执行打包命令时 首先 删除 非项目包，然后执行对应的打包命令。
            if (!dirs.run_in_base) {
                const name$build = 'build';

                [name$build, 'types'].forEach(dirname => {
                    const x = path.join(dirs.run_project, dirname);
                    if (fs.existsSync(x)) {
                        console.log('[I] =>', 'remove directory:', x);
                        fs.rmSync(x, { recursive: true });
                    }
                });

                switch (args.type) {
                    case 'webpackage': {
                        // 执行webpack打包
                        const result = callrun('webpack', {}, {
                            to: name$build,
                        });
                        if (!result) { return; }
                    } break;
                    case 'package': {
                        // 执行tsc
                        const result = callrun('tsc', {}, {
                            to: name$build,
                            declaration: true,
                        });
                        if (!result) { return; }
                    } break;
                    case 'webnext': {
                        const rdir$tsc_js = path.join('dist', 'tsc-webnext', 'js');
                        const dir$tsc_js = path.join(dirs.run_project, rdir$tsc_js);
                        // 执行tsc打包脚本文件
                        {
                            const result = callrun('tsc', {}, {
                                to: rdir$tsc_js,
                            });
                            if (!result) { return; }
                        }

                        // 压缩脚本文件
                        {
                            readdirSync(dir$tsc_js).forEach(file => {
                                console.log('[D] =>', 'minify script:', file);
                                const content = fs.readFileSync(file, 'utf-8');
                                fs.writeFileSync(file, minify_sync(content).code, 'utf-8');
                            });
                        }

                        const dir$build = path.join(dirs.run_project, name$build);
                        // 执行webpack打包非脚本文件
                        {
                            const result = callrun('webpack', {}, {
                                to: name$build,
                                use_module_script: true,

                                enable_mode: true,
                                enable_entry: false,
                                enable_output: true,
                                enable_for_ts: false,
                                enable_for_js: false,
                                enable_for_images: true,
                                enable_for_css: true,
                                enable_for_lib: true,
                                enable_for_favicon: true,
                                enable_for_html: true,
                                enable_devServer: false,
                            });
                            if (!result) { return; }
                        }

                        // webpack打包结束，将脚本文件移入到打包结果目录。
                        {
                            const dir$build_js = path.join(dir$build, 'js');
                            console.log('[I] =>', 'move tsc result to directory: ', dir$build_js);
                            fs.renameSync(dir$tsc_js, dir$build_js);
                        }
                    } break;
                }
            }

            // 删除安装结果
            {
                const x = path.join(dirs.node_modules, process.env.npm_package_name);
                if (fs.existsSync(x)) {
                    console.log('[I] =>', 'remove pack result directory:', x);
                    fs.rmSync(x, { recursive: true });
                }
            }

            // 删除已有版本包
            const dir$packs = path.join(dirs.run_project, 'packs');
            const file$pack = path.join(dir$packs, `${process.env.npm_package_name}-${process.env.npm_package_version}.tgz`);
            {
                const x = file$pack;
                if (fs.existsSync(x)) {
                    console.log('[I] =>', 'remove pack result file:', x);
                    unlinkSync(x);
                }
            }

            // 创建packs目录
            {
                const x = dir$packs;
                if (!fs.existsSync(x)) {
                    console.log('[I] =>', 'make packs directory:', x);
                    mkdirSync(x);
                }
            }

            // 打包
            {
                const result = executeSync(`npm pack --pack-destination=${dir$packs}`);
                if (!result) { return; }
            }
            const dir$tmp = path.join(dirs.base_project, 'tmp');
            const file$packages = path.join(dir$tmp, 'packages.bak');
            const dir$packages = path.join(dir$tmp, 'packages');

            // 备份已安装包
            {
                // 不存在缓存目录则创建
                if (!fs.existsSync(dir$tmp)) { mkdirSync(dir$tmp); }

                // 不存在安装包文件则创建
                if (!fs.existsSync(file$packages)) { fs.writeFileSync(file$packages, dirs.name$base_project, 'utf8'); }

                // 安装包名分隔符
                const SPLITOR = '\r\n';

                // 读取安装包文件内容
                const content$packages = fs.readFileSync(file$packages, 'utf8');
                const packages = content$packages.split(SPLITOR);

                // 没有安装过当前安装包，则记录。
                if (!packages.includes(process.env.npm_package_name)) {
                    packages.push(process.env.npm_package_name);
                    fs.writeFileSync(file$packages, packages.join(SPLITOR), 'utf8');
                }

                // 缓存目录处理
                if (fs.existsSync(dir$packages)) {
                    // 缓存目录存在则先清空缓存目录
                    console.log('[I] =>', `clear packages directory: ${dir$packages}`);
                    fs.rmSync(dir$packages, { recursive: true });
                    mkdirSync(dir$packages);
                } else {
                    // 缓存目录不存在则先创建
                    console.log('[I] =>', `create packages directory: ${dir$packages}`);
                    mkdirSync(dir$packages);
                }

                // 将所有非当前安装包的已安装包缓存
                packages.forEach(p => {
                    if (p === '' || p === process.env.npm_package_name) { return; }
                    const dir$from = path.join(dirs.node_modules, p);
                    if (!fs.existsSync(dir$from)) {
                        console.log('[W] =>', `package<${p}> packed to ${dir$from} is missing.`);
                        return;
                    }
                    const dir$to = path.join(dir$packages, p);
                    console.log('[I] =>', `backup package<${p}> from ${dir$from} to ${dir$to}`);
                    fs.renameSync(dir$from, dir$to);
                });
            }

            // 安装
            {
                // 缓存 package.json 内容
                const file$packageJson = path.join(dirs.base_project, 'package.json');
                const content$packageJson = fs.readFileSync(file$packageJson, 'utf8');
                // 缓存 package-lock.json 内容
                const file$packageLockJson = path.join(dirs.base_project, 'package-lock.json');
                const content$packageLockJson = fs.readFileSync(file$packageLockJson, 'utf8');

                // 安装
                executeSync(`cd ${dirs.base_project} & npm install ${file$pack} --save-dev`);

                // 写回 package.json 内容
                fs.writeFileSync(file$packageJson, content$packageJson, 'utf8');
                // 写回 package-lock.json 内容
                fs.writeFileSync(file$packageLockJson, content$packageLockJson, 'utf8');
            };

            // 还原备份的安装包
            {
                readdirSync(dir$packages, false, false, true).forEach(filePath => {
                    const dir$from = filePath;
                    const dir$to = path.join(dirs.node_modules, path.basename(filePath));
                    console.log('[I] =>', `restore package<${path.basename(filePath)}> from ${dir$from} to ${dir$to}`);
                    fs.renameSync(dir$from, dir$to);
                });
            }
        }
    };



    // =========================== npm install =========================== //
    runners.npmi = {
        argsConfigs: {
            clear: {
                t: 'boolean',
                defaultValue: false,
                range: [true, false],
            },
        },
        run: (args, options) => {
            if (!dirs.run_in_base) {
                console.log('[F] =>', 'this CMD must run in project kui-configs.');
                return;
            }

            if (args.clear === true) {
                // 删除tmp目录
                const dir$tmp = path.join(dirs.base_project, 'tmp');
                if (fs.existsSync(dir$tmp)) {
                    fs.rmSync(dir$tmp, { recursive: true });
                }
            }

            callrun('pack', {}, {});
        }
    };



    // =========================== restore build =========================== //
    runners.restoreBuild = {
        argsConfigs: {
        },
        run: (args, options) => {
            if (dirs.run_in_base) {
                console.log('[F] =>', 'this CMD must run in project except kui-configs.');
                return;
            }

            // 还原build目录
            ['build', 'types', 'packs'].forEach(dirname => {
                const x = path.join(dirs.run_project, dirname);
                if (fs.existsSync(x)) {
                    console.log('[I] =>', 'restore directory:', x);
                    // 先删除目录，再使用git命令还原。（git clean不大合适）
                    fs.rmSync(x, { recursive: true });
                    executeSync(`cd ${dirs.run_project} & git checkout ${dirname}`);
                }
            });
        }
    };



}



// ====================================================== 执行命令 ====================================================== //
function callrun(cmd, args, options) {
    if (typeof cmd === 'undefined') { throw new Error('cmd is a required argument.'); }

    console.log('');
    console.log('[I] =>', 'cmd:', cmd);

    const runner = runners[cmd];

    if (typeof runner === 'undefined') { throw new Error(`cmd must be one of ${Object.keys(runners)}`); }

    args = Object.assign({}, handleArgs(runner.argsConfigs), args);

    console.log('[I] =>', 'args:', args);
    console.log('[I] =>', 'options:', options);

    console.log('');
    const result = runner.run(args, options);
    console.log('');

    return result;
}

callrun(handleArgs({
    cmd: {
        t: 'string',
        range: Object.keys(runners),
    }
}).cmd, {}, {});
